前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • Vue

    • 从源码解读Vue生命周期
    • 组件的本质
    • 有状态组件的设计
    • 设计 VNode
    • 辅助创建 VNode 的 h 函数
    • 自定义渲染器和异步渲染
    • 渲染器之挂载
    • 渲染器的核心 Diff 算法
    • 渲染器之patch
    • 图解 Vue 响应式原理
    • 图解 Vue 异步更新
    • 剖析 Vue 内部运行机制
    • vue响应式原理模拟
    • vue状态管理之vuex
    • 理解Vue的设计思想及实现Vue
    • diff算法深入
    • vue router vuex原理分析
    • Vue3初探响应式原理.
    • vue2源码分析
    • vue组件化实践
  • React

  • Webpack

  • Node

  • Javascript

  • 综合

完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

玩转 Webpack 的 TreeShaking 与 sideEffects 特性|原理篇

文章学习导航
面试总览

30 秒速记

  • 核心判断:构建工具从入口解析依赖图,经转换和优化生成可在目标运行时加载的模块与资源
  • 原理主线:围绕 「Tree Shaking」、「sideEffects」、「写在最后」 建立输入、状态变化与输出之间的因果关系
  • 文章范围:讲解了 Webpack 的 Tree Shaking 和 sideEffects 特性,分析其原理、配置方法及在前端项目中的实际应用,帮助开发者理解如何通过移除未引用代码和合理配置 sideEffects 字段,实现更小的打包体积和更高效的
  • 边界与代价:静态分析受模块语法、动态依赖和副作用声明限制;拆包过细也会增加请求和运行时成本
  • 工程落地:优化前先测构建阶段、产物组成和缓存命中,再针对解析、转换、压缩或运行时逐层处理

Tree Shaking 会标记并移除未使用的导出,而 sideEffects 用来告诉 Webpack 哪些模块不能因未被引用就整段删除。 它依赖静态的 ES Modules,通常由 usedExports 配合代码压缩完成,生产模式会自动启用相关优化。若 Babel 把模块转成 CommonJS,静态分析就可能失效,因此一般将 @babel/preset-env 的 modules 设为 false。对含全局样式或初始化逻辑的模块,不能误标为无副作用。

这篇文章不要按 API 清单来背。先用上面的 Mind Map 建立全局结构,再通过交互 DEMO 观察正常路径和边界路径如何改变状态;阅读正文时重点核对每一步的输入、负责执行的参与者、产生的中间状态以及最终可观察结果。遇到版本敏感结论,要把“历史实现”“当前行为”和“工程兼容策略”分开说明;遇到性能或架构取舍,则用实际指标、失败现象和验证手段支撑判断。

版本校准: 旧文中的 webpack 4、JSONP 更新清单或 react-hot-loader 代码用于解释历史链路,不应直接复制到新项目。当前 webpack-dev-server 4+ 默认启用 HMR,严格 ESM 可使用 import.meta.webpackHot;生产环境不得携带 HMR runtime。以 webpack HMR 官方指南 和项目锁定版本为准。

# Tree Shaking

Tree Shaking 翻译过来的意思就是“摇树”。伴随着摇树的动作,树上的枯树枝和树叶就会掉落下来。

我们这里要介绍的 Tree-shaking 也是同样的道理,不过通过 Tree-shaking “摇掉”的是代码中那些没有用到的部分,这部分没有用的代码更专业的说法应该叫作未引用代码(dead-code)。

Tree-shaking 最早是 Rollup 工具中推出的一个特性,Webpack 从 2.0 过后开始支持这个特性。

我们使用 Webpack 生产模式打包过程中,就会自动开启这个功能,以此来检测我们代码中的未引用代码,然后自动移除它们,从而达到对打包结果的优化。

我们可以先来体验一下这个功能的效果,这里我的源代码非常简单,只有两个文件。

└─ 09-tree-shaking
   ├── src
   │   ├── components.js
   │   └── main.js
   ├── package.json
   └── webpack.config.js
@前端进阶之旅: 代码已经复制到剪贴板

其中 components.js 中导出了一些函数,这些函数各自模拟了一个组件,具体代码如下:

// ./src/components.js
export const Button = () => {
  return document.createElement('button')
  console.log('dead-code')
}
export const Link = () => {
  return document.createElement('a')
}
export const Heading = level => {
  return document.createElement('h' + level)
}
@前端进阶之旅: 代码已经复制到剪贴板

其中 Button 组件函数中,在 return 过后还有一个 console.log() 语句,很明显这句代码永远都不会被执行,所以这个 console.log() 就属于未引用代码。

此外,在 main.js 文件中只是导入了 compnents.js,具体代码如下:

// ./src/main.js
import { Button } from './components'
document.body.appendChild(Button())
@前端进阶之旅: 代码已经复制到剪贴板

注意这里导入 components 模块时,我们只提取了模块中的 Button 成员,那这就导致 components 模块中很多地方都不会被用到,那这些地方就是冗余的,具体冗余部分如下:

// ./src/components.js
export const Button = () => {
  return document.createElement('button')
  // 未引用代码
  console.log('dead-code')
}
// 未引用代码
export const Link = () => {
  return document.createElement('a')
}
// 未引用代码
export const Heading = level => {
  return document.createElement('h' + level)
}
@前端进阶之旅: 代码已经复制到剪贴板

去除冗余代码是生产环境优化中一个很重要的工作,Webpack 的 Tree-shaking 功能就很好地实现了这一点。

我们打开命令行终端,这里我们尝试以 production 模式运行打包,具体命令如下:

$ npx webpack --mode=production
@前端进阶之旅: 代码已经复制到剪贴板

Webpack 的 Tree-shaking 特性在生产模式下会自动开启。打包完成以后我们打开输出的 bundle.js,具体结果如下:

通过搜索你会发现,components 模块中冗余的代码根本没有输出。这就是经过 Tree-shaking 处理过后的效果。

试想一下,如果我们在项目中引入 Lodash 这种工具库,大部分情况下我们只会使用其中的某几个工具函数,而其他没有用到的部分就都属于冗余代码。通过 Tree-shaking 就可以极大地减少最终打包后 bundle 的体积。

需要注意的是,Tree-shaking 并不是指 Webpack 中的某一个配置选项,而是一组功能搭配使用过后实现的效果,这组功能在生产模式下都会自动启用,所以使用生产模式打包就会有 Tree-shaking 的效果。

# 开启 Tree Shaking

由于目前官方文档中对于 Tree-shaking 的介绍有点混乱,所以我们这里再来介绍一下在其他模式下,如何一步一步手动开启 Tree-shaking。通过这个过程,还可以顺便了解 Tree-shaking 的工作过程和 Webpack 其他的一些优化功能。

这里还是使用上述的案例结构,我们再次运行 Webpack 打包,不过这一次我们不再使用 production 模式,而是使用 none,也就是不开启任何内置功能和插件,具体命令如下:

$ npx webpack --mode=none
@前端进阶之旅: 代码已经复制到剪贴板

打包完成过后,我们再次找到输出的 bundle.js 文件,具体结果如下:

我们这里注意一下 components 对应的这个模块,虽然外部没有使用这里的 Link 函数和 Heading 函数,但是仍然导出了它们,具体如下图所示:

显然这种导出是没有任何意义的。

明确目前打包结果的状态过后,我们打开 Webpack 的配置文件,在配置对象中添加一个 optimization 属性,这个属性用来集中配置 Webpack 内置优化功能,它的值也是一个对象。

在 optimization 对象中我们可以先开启一个 usedExports 选项,表示在输出结果中只导出外部使用了的成员,具体配置代码如下:

// ./webpack.config.js
/** @type {import('webpack').Configuration} */
module.exports = {
  // ... 其他配置项
  optimization: {
    // 模块只导出被使用的成员
    usedExports: true
  }
}
@前端进阶之旅: 代码已经复制到剪贴板

配置完成后,重新打包,然后我们再来看一下输出的 bundle.js,具体结果如下图:

此时你会发现 components 模块所对应的函数,就不再导出 Link 和 Heading 这两个函数了,那它们对应的代码就变成了未引用代码。而且如果你使用的是 VS Code,会发现 VS Code 将这两个函数名的颜色变淡了,这是为了表示它们未被引用。

对于这种未引用代码,如果我们开启压缩代码功能,就可以自动压缩掉这些没有用到的代码。

我们可以回到配置文件中,尝试在 optimization 配置中开启 minimize,具体配置如下:

// ./webpack.config.js
/** @type {import('webpack').Configuration} */
module.exports = {
  // ... 其他配置项
  optimization: {
    // 模块只导出被使用的成员
    usedExports: true,
    // 压缩输出结果
    minimize: true
  }
}
@前端进阶之旅: 代码已经复制到剪贴板

然后再次回到命令行重新运行打包,具体结果如下图所示:

仔细查看打包结果,你会发现,Link 和 Heading 这些未引用代码都被自动移除了。

这就是 Tree-shaking 的实现,整个过程用到了 Webpack 的两个优化功能:

  • usedExports - 打包结果中只导出外部用到的成员;
  • minimize - 压缩打包结果。

如果把我们的代码看成一棵大树,那你就可以这样理解:

  • usedExports 的作用就是标记树上哪些是枯树枝、枯树叶;
  • minimize 的作用就是负责把枯树枝、枯树叶摇下来。

# 合并模块 *

除了 usedExports 选项之外,我们还可以使用一个 concatenateModules 选项继续优化输出。

普通打包只是将一个模块最终放入一个单独的函数中,如果我们的模块很多,就意味着在输出结果中会有很多的模块函数。

concatenateModules 配置的作用就是尽可能将所有模块合并到一起输出到一个函数中,这样既提升了运行效率,又减少了代码的体积。

我们回到配置文件中,这里我们在 optimization 属性中开启 concatenateModules。同时,为了更好地看到效果,我们先关闭 minimize,具体配置如下:

// ./webpack.config.js
/** @type {import('webpack').Configuration} */
module.exports = {
  // ... 其他配置项
  optimization: {
    // 模块只导出被使用的成员
    usedExports: true,
    // 尽可能合并每一个模块到一个函数中
    concatenateModules: true,
    // 压缩输出结果
    minimize: false
  }
}
@前端进阶之旅: 代码已经复制到剪贴板

然后回到命令行终端再次运行打包。那此时 bundle.js 中就不再是一个模块对应一个函数了,而是把所有的模块都放到了一个函数中,具体结果如下:

← 利用 Webpack CodeSplitting 完成复杂应用拆包如何配置 Webpack SourceMap 的最佳实践 →

fe
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • Vue

    • 从源码解读Vue生命周期
    • 组件的本质
    • 有状态组件的设计
    • 设计 VNode
    • 辅助创建 VNode 的 h 函数
    • 自定义渲染器和异步渲染
    • 渲染器之挂载
    • 渲染器的核心 Diff 算法
    • 渲染器之patch
    • 图解 Vue 响应式原理
    • 图解 Vue 异步更新
    • 剖析 Vue 内部运行机制
    • vue响应式原理模拟
    • vue状态管理之vuex
    • 理解Vue的设计思想及实现Vue
    • diff算法深入
    • vue router vuex原理分析
    • Vue3初探响应式原理.
    • vue2源码分析
    • vue组件化实践
  • React

  • Webpack

  • Node

  • Javascript

  • 综合